SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
12.4 KB · 253 lines tsx
Raw Blame History
1import { ArrowLeftRight } from 'lucide-react';2import { ScrollX } from '@/components/models/scroll-x';3import type { Metadata } from 'next';4import Link from 'next/link';5import { notFound } from 'next/navigation';6import { ComparabilityBadge, TrustBadge } from '@/components/models/badges';7import { fmtScoreUnit } from '@/components/models/shared';8import { Chip, EntityBadge } from '@/components/ui/badges';9import { DataTable, Td, Th } from '@/components/ui/data-table';10import { EntityLink } from '@/components/ui/entity';11import { Container, Note, PageHeader } from '@/components/ui/section';12import { EmptyState, Unavailable } from '@/components/ui/unavailable';13import { ApiError, apiD1, safe } from '@/lib/api';14import { cn } from '@/lib/cn';15import { fmtDate, fmtInt, fmtTokens, fmtUsdPerM, fmtValue, num } from '@/lib/format';16import { routes, SITE_NAME, SITE_URL } from '@/lib/site';17import type { DiffDelta, ModelDiffPayload } from '@/lib/types';1819/*20  /models/[a]/diff/[b] — only the dimensions whose observed values differ, with deltas: context 200K → 1M (+400%), price −34%,21  benchmark ± points (comparable groups only), capability lists as added / removed. Shareable; linked from /compare.22*/2324type Params = { params: Promise<{ slug: string; b: string }> };25export const revalidate = 300;2627async function load(a: string, b: string): Promise<{ res: ModelDiffPayload | null; error: string | null }> {28  try {29    return { res: await apiD1.modelDiff(a, b), error: null };30  } catch (e) {31    if (e instanceof ApiError && e.notFound) notFound();32    if (e instanceof ApiError && (e.status === 400 || e.status === 422)) return { res: null, error: e.detail ?? 'These two entities cannot be diffed.' };33    return { res: null, error: null };34  }35}3637export async function generateMetadata({ params }: Params): Promise<Metadata> {38  const { slug, b } = await params;39  const res = await safe(apiD1.modelDiff(slug, b));40  const canonical = `/models/${encodeURIComponent(slug)}/diff/${encodeURIComponent(b)}`;41  if (!res) return { title: 'Model diff', robots: { index: false }, alternates: { canonical } };42  const title = `${res.a.name} vs ${res.b.name} — what differs`;43  const description = `${res.dimensions.length} differing dimensions between ${res.a.name} and ${res.b.name}: context, prices, benchmarks (comparable groups only), capabilities — each value with its source. ${SITE_NAME}.`;44  return { title, description, alternates: { canonical }, openGraph: { title: `${title} | ${SITE_NAME}`, description, url: `${SITE_URL}${canonical}`, type: 'article' } };45}4647function isNumDelta(d: DiffDelta): d is { absolute: number; percent: number | null } {48  return !!d && 'absolute' in d;49}50function isListDelta(d: DiffDelta): d is { added: unknown[]; removed: unknown[] } {51  return !!d && 'added' in d;52}5354function cell(kind: string, key: string, v: unknown, unit?: string): string {55  if (v === null || v === undefined || v === '' || (Array.isArray(v) && v.length === 0)) return 'Unavailable';56  if (kind === 'date') return fmtDate(String(v));57  if (kind === 'bool') return v ? 'Yes' : 'No';58  if (kind === 'list') return (Array.isArray(v) ? v : [v]).map(String).join(', ');59  if (kind === 'number') {60    if (/per_mtok/.test(key)) return fmtUsdPerM(v);61    if (key.startsWith('bench:')) return fmtScoreUnit(num(v), unit ?? null);62    if (/context_length|max_output_tokens/.test(key)) return fmtTokens(v);63    return fmtValue(v, key);64  }65  return typeof v === 'string' ? v : fmtValue(v, key);66}6768/** "+39 days" / "−2 months" between two ISO dates (month precision when either date is YYYY-MM). */69function dayDelta(a: string, b: string): string {70  const monthly = a.length <= 7 || b.length <= 7;71  const ta = new Date(a.length === 7 ? `${a}-01` : a.length === 4 ? `${a}-01-01` : a).getTime();72  const tb = new Date(b.length === 7 ? `${b}-01` : b.length === 4 ? `${b}-01-01` : b).getTime();73  if (!Number.isFinite(ta) || !Number.isFinite(tb)) return 'differs';74  const days = Math.round((tb - ta) / 86400000);75  const sign = days > 0 ? '+' : '−';76  const abs = Math.abs(days);77  if (monthly || abs >= 90) {78    const months = Math.round(abs / 30.44);79    return `${sign}${months} month${months === 1 ? '' : 's'}`;80  }81  return `${sign}${abs} day${abs === 1 ? '' : 's'}`;82}8384function fmtPct(p: number | null): string | null {85  if (p === null || !Number.isFinite(p)) return null;86  const sign = p > 0 ? '+' : p < 0 ? '−' : '';87  const abs = Math.abs(p);88  return `${sign}${abs >= 100 ? abs.toFixed(0) : abs.toFixed(abs < 10 ? 1 : 0)}%`;89}9091export default async function ModelDiffPage({ params }: Params) {92  const { slug, b } = await params;93  const { res, error } = await load(slug, b);94  const canonical = `/models/${encodeURIComponent(slug)}/diff/${encodeURIComponent(b)}`;95  if (!res)96    return (97      <Container>98        <PageHeader eyebrow="Model diff" title="What differs" />99        <div className="pb-16">{error ? <EmptyState title="These two entities cannot be diffed">{error}</EmptyState> : <Unavailable what="Model diff" reason="The API did not answer. Try again in a moment." />}</div>100      </Container>101    );102  const attr = res.dimensions.filter((d) => d.source !== 'results' && d.source !== 'prices');103  const prices = res.dimensions.filter((d) => d.source === 'prices');104  const bench = res.dimensions.filter((d) => d.source === 'results');105  const lists = res.dimensions.filter((d) => isListDelta(d.delta));106  const ld = { '@context': 'https://schema.org', '@type': 'Dataset', name: `${res.a.name} vs ${res.b.name} — differences`, url: `${SITE_URL}${canonical}`, description: res.note ?? undefined, about: [res.a, res.b].map((e) => ({ '@type': 'SoftwareApplication', name: e.name, url: `${SITE_URL}${routes.entity(e)}` })) };107108  const Row = ({ d }: { d: ModelDiffPayload['dimensions'][number] }) => {109    const comp = res.comparability?.[d.key];110    const nd = isNumDelta(d.delta) ? d.delta : null;111    const better = nd && d.kind === 'number' ? (d.source === 'prices' ? nd.absolute < 0 : (d.higher_is_better ?? true) ? nd.absolute > 0 : nd.absolute < 0) : null;112    return (113      <tr>114        <Td primary className="align-top">115          {d.key.startsWith('bench:') && d.benchmark ? (116            <Link href={routes.benchmark(d.benchmark) + (d.metric && d.config_key ? `?metric=${encodeURIComponent(d.metric)}&config_key=${encodeURIComponent(d.config_key)}` : '')} className="text-ink hover:text-accent hover:underline">117              {d.label.split(' · ')[0]}118            </Link>119          ) : (120            d.label121          )}122          <span className="block text-[11px] text-ink-3">123            {d.key.startsWith('bench:') ? d.label.split(' · ').slice(1).join(' · ') : d.unit}124          </span>125          {comp && (126            <span className="mt-0.5 flex flex-wrap items-center gap-1">127              <ComparabilityBadge level={comp.level} reasons={comp.reasons} />128              {comp.trust?.[res.a.id] && <TrustBadge level={comp.trust[res.a.id]?.level} label={comp.trust[res.a.id]?.label} />}129            </span>130          )}131        </Td>132        <Td label={res.a.name} className="tnum align-top">133          {cell(d.kind, d.key, d.a, d.unit)}134        </Td>135        <Td label={res.b.name} className="tnum align-top">136          {cell(d.kind, d.key, d.b, d.unit)}137        </Td>138        <Td label="Delta (b − a)" className="tnum align-top">139          {nd ? (140            <span className={cn('font-medium', better === true && 'text-positive', better === false && 'text-danger')}>141              {d.source === 'prices' ? `${nd.absolute > 0 ? '+' : '−'}${fmtUsdPerM(Math.abs(nd.absolute))}` : d.key.startsWith('bench:') ? `${nd.absolute > 0 ? '+' : '−'}${Math.abs(nd.absolute).toFixed(Math.abs(nd.absolute) < 10 ? 2 : 1)}${d.unit === '%' ? ' pt' : ''}` : `${nd.absolute > 0 ? '+' : '−'}${fmtValue(Math.abs(nd.absolute), d.key)}`}142              {fmtPct(nd.percent) && <span className="ml-1.5 text-xs font-normal text-ink-3">{fmtPct(nd.percent)}</span>}143            </span>144          ) : isListDelta(d.delta) ? (145            <span className="flex flex-wrap gap-1">146              {d.delta.added.map((x) => (147                <Chip key={`+${String(x)}`} className="text-positive bg-positive-soft">148                  + {String(x)}149                </Chip>150              ))}151              {d.delta.removed.map((x) => (152                <Chip key={`-${String(x)}`} className="text-danger bg-danger-soft">153                  − {String(x)}154                </Chip>155              ))}156            </span>157          ) : d.kind === 'date' && typeof d.a === 'string' && typeof d.b === 'string' ? (158            <span className="tnum text-ink-2">{dayDelta(d.a, d.b)}</span>159          ) : (160            <span className="text-ink-3">differs</span>161          )}162        </Td>163      </tr>164    );165  };166  const Table = ({ rows, caption }: { rows: ModelDiffPayload['dimensions']; caption: string }) => (167    <ScrollX><DataTable caption={caption} compact>168      <thead>169        <tr>170          <Th>Dimension</Th>171          <Th>{res.a.name}</Th>172          <Th>{res.b.name}</Th>173          <Th>Delta (b − a)</Th>174        </tr>175      </thead>176      <tbody>177        {rows.map((d) => (178          <Row key={d.key} d={d} />179        ))}180      </tbody>181    </DataTable></ScrollX>182  );183184  return (185    <Container wide>186      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />187      <PageHeader188        eyebrow={189          <>190            <span>Model diff</span>191            <EntityBadge type="model" small />192          </>193        }194        title={195          <>196            {res.a.name} <span className="text-ink-3">vs</span> {res.b.name}197          </>198        }199        lede={`${fmtInt(res.dimensions.length)} dimensions differ. ${res.note ?? 'Only dimensions with differing observed values are listed; numeric delta = (b − a) / a.'}`}200        aside={201          <div className="flex flex-wrap gap-2 text-sm">202            <Link href={`/models/${encodeURIComponent(res.b.slug)}/diff/${encodeURIComponent(res.a.slug)}`} className="inline-flex h-9 items-center gap-1.5 border border-rule px-2.5 text-ink-2 hover:border-rule-strong hover:text-ink">203              <ArrowLeftRight className="size-3.5" aria-hidden /> Swap204            </Link>205            <Link href={routes.compare([res.a.slug, res.b.slug])} className="inline-flex h-9 items-center bg-ink px-3 font-medium text-canvas hover:opacity-90">206              Full comparison →207            </Link>208          </div>209        }210      >211        <p className="mt-4 flex flex-wrap gap-x-6 gap-y-1 text-sm text-ink-2">212          <span>213            a · <EntityLink e={res.a} className="font-medium" /> {res.a.organization && <span className="text-ink-3">{res.a.organization.name}</span>}214          </span>215          <span>216            b · <EntityLink e={res.b} className="font-medium" /> {res.b.organization && <span className="text-ink-3">{res.b.organization.name}</span>}217          </span>218        </p>219      </PageHeader>220      <div className="space-y-10 pb-16">221        {res.dimensions.length === 0 && <EmptyState title="No recorded difference">Every observed dimension has the same value for both models — or neither has a value.</EmptyState>}222        {attr.length > 0 && (223          <section>224            <p className="eyebrow mb-2">225              Specification & capabilities <span className="tnum text-ink-3">{attr.length}</span>226            </p>227            <Table rows={attr} caption="Differing specification dimensions" />228          </section>229        )}230        {prices.length > 0 && (231          <section>232            <p className="eyebrow mb-2">233              Pricing <span className="tnum text-ink-3">{prices.length}</span>234            </p>235            <Table rows={prices} caption="Differing price dimensions" />236            <Note className="mt-2">Best (cheapest) current offer across providers, USD per 1M tokens. A negative delta means b is cheaper.</Note>237          </section>238        )}239        {bench.length > 0 && (240          <section>241            <p className="eyebrow mb-2">242              Benchmarks <span className="tnum text-ink-3">{bench.length}</span>243            </p>244            <Table rows={bench} caption="Differing benchmark scores" />245            <Note className="mt-2">Only comparability groups where both models have a current result. Partially comparable = same task, conditions (effort, temperature, judge) differ. Not a verdict.</Note>246          </section>247        )}248        {lists.length > 0 && <Note>List dimensions ({lists.map((l) => l.label).join(', ')}) show elements added (+) and removed (−) going from a to b.</Note>}249      </div>250    </Container>251  );252}253